<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>瀑布流</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        background-color: #eee;
      }
      .container {
        margin: 0 auto;
        height: auto;
        position: relative;
      }

      .item {
        width: 200px;
        border-radius: 6px;
        position: absolute;
        overflow: hidden;
      }
      
      .img-box {
        font-size: 0;
      }
      
      .item img {
        width: 100%;
      }
    </style>
  </head>
  <body>
    <a href="https://www.bilibili.com/video/BV18t4y1X7sx/?spm_id_from=333.337.search-card.all.click&vd_source=77038b58f9d7cc87196f5aeec9090c45">b站源地址</a>
    <div class="container">
    </div>
    <script>
      /**
       * 思路解析，最大的问题就是知道最低高度是哪个
       */
      class Masonry {
        constructor(container, options = {}) {
          const {
            column_width = 200,
            gap_width = 15,
            gap_height = 15,
          } = options
          
          this.column_width = column_width
          this.gap_width = gap_width
          this.gap_height = gap_height
          
          this.container = container
          this.columnCount = 0
          this.columnHeights = []
        }
        
        init() {
          this.resetColumnCount()
          this.resetHeights()
          this.manageCells()
          this.bindEvent()
        }
        
        bindEvent() {
          
          window.addEventListener('scrollend', () => {
            const viewPortBottom = document.documentElement.clientHeight + document.documentElement.scrollTop
            if (viewPortBottom > Math.min(...this.columnHeights)) {
              this.manageCells()
            }
          })
          
          const config = { attributes: false, childList: true, subtree: false };
          const observer = new MutationObserver(() => {
            const viewPortBottom = document.documentElement.clientHeight + document.documentElement.scrollTop
            if (viewPortBottom > Math.min(...this.columnHeights)) {
              this.manageCells()
              observer.disconnect();
            }
          });
          // 以上述配置开始观察目标节点
          observer.observe(this.container, config);
        }
        
        resetColumnCount() {
          this.columnCount = Math.max(2, Math.floor((document.body.offsetWidth + this.gap_width) / (this.column_width + this.gap_width)))
        }
        
        resetHeights() {
          this.columnHeights = new Array(this.columnCount).fill(0)
          this.container.style.width = this.columnCount * (this.column_width + this.gap_width) - this.gap_width
        }
        
        manageCells() {
          
          this.loadMoreCells()
        }
        
        async loadMoreCells() {
          const cells = await this.appendCells()
          this.adjustCells(cells)
        }
        
        async appendCells() {
          const fragment = document.createDocumentFragment()
          const cells = []
          for (var i = 0; i < 8; i++) {
            const data = await this.fetchData()
            const cell = document.createElement('div')
            cell.classList.add('item')
            cell.innerHTML = `
              <div class="img-box">
                <img src="${URL.createObjectURL(data)}" alt="" width="${this.column_width}" height="100%" />
              </div>
            `
            cells.push(cell)
            fragment.appendChild(cell)
          }
          
          this.container.appendChild(fragment)
          return cells
        }
        
        adjustCells(cells) {
          cells.forEach(cell => {
            const minHeight = Math.min(...this.columnHeights)
            const indexOfMinHeight = this.columnHeights.indexOf(minHeight)
            cell.style.left = indexOfMinHeight * (this.column_width + this.gap_width) + 'px'
            cell.style.top = minHeight + 'px'
            this.columnHeights[indexOfMinHeight] = minHeight + this.gap_width + cell.offsetHeight
          })
          this.container.style.height = Math.max(...this.columnHeights) + 'px'
        }
        
        async fetchData() {
          return fetch(`https://globe.cdn.media-library.fun/Image/Phone/image-phone-${this.getNumber()}.webp`)
            .then(res => res.blob())
        }
        
        getNumber() {
          return Math.floor(Math.random() * 1000 + 100)
        }
      }
      const container = document.querySelector('.container')
      const masonry = new Masonry(container)
      masonry.init()
    </script>
  </body>
</html>